﻿namespace MefContrib.Models.Provider
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition.Primitives;
    using System.Linq;
    using System.Linq.Expressions;
    using MefContrib.Models.Provider.Validation;

    /// <summary>
    /// Defines a <see cref="CompositionContainer"/> with the capability of applying a condition on exports
    /// which matches any of the registered contracts.
    /// </summary>
    public class ConditionalCompositionContainer : CompositionContainer
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ConditionalCompositionContainer"/> class, using
        /// the specified <see cref="CompositionContainer"/>.
        /// </summary>
        /// <param name="catalog">The catalog to add to the container.</param>
        public ConditionalCompositionContainer(ComposablePartCatalog catalog)
            : base(catalog)
        {
            this.Conditions = new Dictionary<string, Func<Export, bool>>();
        }

        /// <summary>
        /// Gets or sets filters which are availble to the container.
        /// </summary>
        /// <value>A <see cref="IDictionary{TKey,TValue}"/> object. The of the dictionairy should be the contract name.</value>
        protected IDictionary<string, Func<Export, bool>> Conditions { get; set; }

        /// <summary>
        /// Registers a member with the container.
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> that the member, identified by the <paramref name="expression"/> parameter, belongs to.</typeparam>
        /// <param name="expression">The expression used to identify the targeted member.</param>
        /// <returns>A <see cref="ConditionalCompositionContainer"/> object.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public virtual ConditionalCompositionContainer Register<T>(Expression<Action<T>> expression)
        {
            expression
                .Require("expression")
                .NotNull();

            this.Conditions.Add(this.ExtractMemberFromExpression(expression), null);

            return this;
        }

        /// <summary>
        /// Registers a member with the container.
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> that the member, identified by the <paramref name="expression"/> parameter, belongs to.</typeparam>
        /// <param name="expression">The expression used to identify the targeted member.</param>
        /// <returns>A <see cref="ConditionalCompositionContainer"/> object.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public virtual ConditionalCompositionContainer Register<T>(Expression<Func<T, object>> expression)
        {
            expression
                .Require("expression")
                .NotNull();

            this.Conditions.Add(this.ExtractMemberFromExpression(expression), null);

            return this;
        }

        /// <summary>
        /// Assigns a condition to the previously registered contract.
        /// </summary>
        /// <param name="condition">A <see cref="Func{T,TResult}"/> object containing the condition to apply to the contract.</param>
        /// <returns>A <see cref="ConditionalCompositionContainer"/> object.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public virtual ConditionalCompositionContainer Where(Func<Export, bool> condition)
        {
            condition
                .Require("condition")
                .NotNull();

            this.Conditions[this.Conditions.Last().Key] = condition;

            return this;
        }

        /// <summary>
        /// Extracts the name of the member from the provided expression tree.
        /// </summary>
        /// <param name="expression">The expression to extract the name from.</param>
        /// <returns>A <see cref="String"/> containing the name of the member if it could be extracted; otherwise <see langword="null"/>.</returns>
        protected virtual string ExtractMemberFromExpression(Expression expression)
        {
            string memberName = null;

            switch (expression.NodeType)
            {
                case ExpressionType.Lambda:
                    return this.ExtractMemberFromExpression(((LambdaExpression)expression).Body);
                case ExpressionType.Call:
                    memberName = ((MethodCallExpression)expression).Method.Name;
                    break;
                case ExpressionType.Convert:
                    return this.ExtractMemberFromExpression(((UnaryExpression)expression).Operand);
                case ExpressionType.MemberAccess:
                    memberName = ((MemberExpression)expression).Member.Name;
                    break;
                case ExpressionType.Parameter:
                    memberName = expression.Type.Name;
                    break;
            }

            return memberName;
        }

        /// <summary>
        /// Gets the exports which matches the provided <see cref="ImportDefinition"/>.
        /// </summary>
        /// <param name="definition">The import definition to find exports for.</param>
        /// <returns>A <see cref="IEnumerable{T}"/> object, containing the matched exports.</returns>
        protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition composition)
        {
            ProviderImportDefinition import =
                definition as ProviderImportDefinition;

            if (import != null)
            {
                Func<Export, bool> condition;
                if (this.Conditions.TryGetValue(import.Member.Name, out condition))
                {
                    return base.GetExportsCore(definition, composition).Where(condition);
                }
            }

            return base.GetExportsCore(definition, composition);
        }
    }
}